/*
 * JOCL - Java bindings for OpenCL
 *
 * Copyright 2014 Marco Hutter - http://www.jocl.org/
 */
package sw.marco;
 
import static org.jocl.CL.clGetDeviceInfo;
 
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.Random;
 
import org.jocl.CL;
import org.jocl.Pointer;
import org.jocl.Sizeof;
import org.jocl.cl_device_id;
import sw.marco.JOCLSmithWaterman.AlignmentMatrix;
 
/**
 * Utility methods for the Smith-Waterman JOCL sample
 */
class Utils
{
    /**
     * Create a string with the specified length, consisting of random letters
     * from ['A' ... 'Z']
     *
     * @param length The length of the string
     * @param random The random number generator
     * @return The string
     */
    static String createRandomString(int length, Random random)
    {
        StringBuilder sb = new StringBuilder();
        for (int i=0; i< length; i++)
        {
            sb.append((char)('A'+random.nextInt(26)));
        }
        return sb.toString();
    }
   
    /**
     * Reads the contents of the file with the given name, and returns
     * it as a string. If there is any IO error, then an error message
     * will be printed and <code>null</code> will be returned.
     *  
     * @param fileName The name of the file to read
     * @return The contents of the file, or <code>null</code> if an error
     * occurred
     */
    static String read(String fileName)
    {
        InputStream inputStream = null;
        try
        {  
            inputStream = new FileInputStream(fileName);
            String result = read(inputStream);
            return result;
        }
        catch (IOException e)
        {
            e.printStackTrace();
            return null;
        }
        finally
        {
            if (inputStream != null)
            {
                try
                {
                    inputStream.close();
                }
                catch (IOException e)
                {
                    e.printStackTrace();
                }
            }
        }
    }
 
    /**
     * Reads the lines that are contained in the given input stream and
     * returns them as a string. The caller is responsible for closing
     * the given stream.
     *  
     * @param inputStream The input stream to read.
     * @return The contents of the stream
     * @throws IOException If an IO error occurs
     */
    static String read(InputStream inputStream) throws IOException
    {
        BufferedReader br = new BufferedReader(
            new InputStreamReader(inputStream));
        StringBuilder sb = new StringBuilder();
        String line = null;
        while (true)
        {
            line = br.readLine();
            if (line == null)
            {
                break;
            }
            sb.append(line+"\n");
        }
        return sb.toString();
    }
   
    /**
     * Returns the CL_DEVIVE_MAX_WORK_GROUP_SIZE of the given device
     *
     * @param device The device
     * @return The maximum work group size
     */
    static int getMaxWorkGroupSize(cl_device_id device)
    {
        ByteBuffer buffer = ByteBuffer.
            allocate(Sizeof.size_t).
            order(ByteOrder.nativeOrder());
        clGetDeviceInfo(device, CL.CL_DEVICE_MAX_WORK_GROUP_SIZE,
                Sizeof.size_t, Pointer.to(buffer), null);
        if (Sizeof.size_t == 4)
        {
            return buffer.getInt();
        }
        else
        {
            return (int)buffer.getLong();
        }
    }
   
    /**
     * Initialize the alignment matrix:
     * <ul>
     *   <li>Fill it with all zeros</li>
     *   <li>  
     *     Then fill the first row with values 0*PREFIX_VALUE,
     *     1*PREFIX_VALUE, ... (sizeX-1)*PREFIX_VALUE
     *   </li>
     *   <li>  
     *     Then fill the first column with values 0*PREFIX_VALUE,
     *     1*PREFIX_VALUE, ... (sizeY-1)*PREFIX_VALUE
     *   </li>
     * </ul>  
     *
     * @param matrix The matrix
     * @param sizeX The size of the matrix
     * @param sizeY The size of the matrix
     */
    static void initMatrix(int matrix[], int sizeX, int sizeY)
    {
        Arrays.fill(matrix, 0);
        int x = 0;
        int y = 0;
        for (x=0; x<sizeX; x++)
        {
            int index = x + y * sizeX;
            matrix[index] = x * JOCLSmithWaterman.PREFIX_VALUE;
        }
        x = 0;
        for (y=0; y<sizeY; y++)
        {
            int index = x + y * sizeX;
            matrix[index] = y * JOCLSmithWaterman.PREFIX_VALUE;
        }
    }
   
    /**
     * Print the given alignment matrix
     *
     * @param alignmentMatrix The alignment matrix
     */
    static void print(AlignmentMatrix alignmentMatrix)
    {
        print(
            alignmentMatrix.matrix,
            alignmentMatrix.sizeX,
            alignmentMatrix.sizeY,
            alignmentMatrix.s0,
            alignmentMatrix.s1);
    }
   
    /**
     * Print the alignment matrix for the given strings. The strings are
     * assumed to be padded so that s0.length==sizeX and s1.length==sizeY.
     *
     * @param matrix The matrix
     * @param sizeX The size of the matrix
     * @param sizeY The size of the matrix
     * @param s0 The first string
     * @param s1 The second string
     */
    static void print(
        int[] matrix, int sizeX,
        int sizeY, byte s0[], byte s1[])
    {
        int cellSize = 4;
        String format = "%"+cellSize+"s";
        System.out.printf(format, "");
        for (int x=0; x<sizeX; x++)
        {
            char c = ' ';
            if (x > 0)
            {
                c = (char) s0[x-1];
                if (c == 0)
                {
                    c = '.';
                }
            }
            System.out.printf(format, String.valueOf(c));
        }
        System.out.println();
 
        for (int y=0; y<sizeY; y++)
        {
            char c = ' ';
            if (y > 0)
            {
                c = (char) s1[y-1];
                if (c == 0)
                {
                    c = '.';
                }
            }
            System.out.printf(format, String.valueOf(c));
            for (int x=0; x<sizeX; x++)
            {
                int index = x + y*sizeX;
                int value = matrix[index];
                System.out.printf(format, String.valueOf(value));
            }
            System.out.println();
        }
    }
 
    /**
     * Checks whether the given alignment matrices are equal. That is,
     * whether they contain the same values in the range that is present
     * in both matrices. (If one of the matrices is padded to be larger
     * than the other, then the padded part is ignored)
     *
     * @param a0 The first matrix
     * @param a1 The second matrix
     * @return Whether the matrices are equal
     */
    static boolean equal(AlignmentMatrix a0, AlignmentMatrix a1)
    {
        int minSizeX = Math.min(a0.sizeX, a1.sizeX);
        int minSizeY = Math.min(a0.sizeY, a1.sizeY);
        for (int x=0; x<minSizeX; x++)
        {
            for (int y=0; y<minSizeY; y++)
            {
                int index0 = x + y * a0.sizeX;
                int index1 = x + y * a1.sizeX;
                int value0 = a0.matrix[index0];
                int value1 = a1.matrix[index1];
                if (value0 != value1)
                {
                    System.out.println("At "+x+" "+y+" found values "+
                        value0+" and "+value1);
                    return false;
                }
            }
        }
        return true;
    }
   
   
    /**
     * Private constructor to prevent instantiation
     */
    private Utils()
    {
        // Private constructor to prevent instantiation
    }
}